﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Administration;
using System.Collections.ObjectModel;
using Microsoft.SharePoint;
using System.Threading;
using System.ServiceProcess;

namespace SPALM
{
    public enum LogLevel
    {
        High,
        Low,
        Normal
    }

    public class DeploymentOperation
    {
        internal List<string> exceptions = new List<string>();
        internal Deployment parentTask;
        internal int ParsedRetries;
        internal int ParsedWaitSecondsAfterFinish;
        internal bool ParsedSimulateDeployment;

        public DeploymentOperation(Deployment parentTask, int ParsedRetries, int ParsedWaitSecondsAfterFinish, bool ParsedSimulateDeployment)
        {
            // TODO: Complete member initialization
            this.parentTask = parentTask;
            this.ParsedRetries = ParsedRetries;
            this.ParsedWaitSecondsAfterFinish = ParsedWaitSecondsAfterFinish;
            this.ParsedSimulateDeployment = ParsedSimulateDeployment;
        }


        internal string FormatHeader(string message)
        {
            return message;
            /*
            message = "***** " + message + " ";
            message = message.PadRight(78, '*');
            return message;
             * */
        }

        internal bool RestartService(string serviceName)
        {
            try
            {
                ServiceController sc = new ServiceController(serviceName);
                if (sc.CanStop)
                {
                    LogMessage("Stopping service " + serviceName, LogLevel.Normal);
                    sc.Stop();
                    sc.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 0, 20));
                }

                LogMessage("Starting service " + serviceName, LogLevel.Normal);
                sc.Start();
                sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 0, 20));

                if (sc.Status == ServiceControllerStatus.Running)
                {
                    return true;
                }
            }
            catch
            {

            }
            return false;
        }

        public bool Run()
        {
            CheckPreconditions();
            HandleExceptions();

            PrepareSystem();
            HandleExceptions();

            RunOperation();
            HandleExceptions();

            if (ParsedWaitSecondsAfterFinish > 0)
            {
                LogMessage("Waiting " + ParsedWaitSecondsAfterFinish + " milliseconds...", LogLevel.High);
                System.Threading.Thread.Sleep(ParsedWaitSecondsAfterFinish);
            }
            LogMessage(" ", LogLevel.High);

            ValidateSystem();
            HandleExceptions();



            return true;
        }

        public virtual void CheckPreconditions()
        {

        }

        public virtual void PrepareSystem()
        {

        }

        public virtual void RunOperation()
        {

        }

        public virtual void ValidateSystem()
        {

        }

        internal void LogWarning(string message)
        {
            parentTask.Log.LogWarning(message);
        }

        internal void LogMessage(string message, LogLevel logLevel)
        {
            parentTask.Log.LogMessageFromText(message, GetMessageImportanceFromLogLevel(logLevel));
        }

        internal void LogException(string message)
        {
            parentTask.Log.LogError(message);
        }

        private Microsoft.Build.Framework.MessageImportance GetMessageImportanceFromLogLevel(LogLevel logLevel)
        {
            switch (logLevel)
            {
                case LogLevel.High:
                    return Microsoft.Build.Framework.MessageImportance.High;
                case LogLevel.Normal:
                    return Microsoft.Build.Framework.MessageImportance.Normal;
                default:
                    return Microsoft.Build.Framework.MessageImportance.Low;
            }
        }

        internal Collection<SPWebApplication> GetAllContentWebApps()
        {
            Collection<SPWebApplication> contentWebApps = new Collection<SPWebApplication>();
            foreach (SPWebApplication app2 in Microsoft.SharePoint.Administration.SPWebService.ContentService.WebApplications)
            {
                contentWebApps.Add(app2);
            }
            return contentWebApps;
        }


        internal Collection<SPWebApplication> GetAllWebApps()
        {
            Collection<SPWebApplication> contentWebApps = new Collection<SPWebApplication>();
            foreach (SPWebApplication app2 in Microsoft.SharePoint.Administration.SPWebService.ContentService.WebApplications)
            {
                contentWebApps.Add(app2);
            }
            foreach (SPWebApplication app1 in Microsoft.SharePoint.Administration.SPWebService.AdministrationService.WebApplications)
            {
                contentWebApps.Add(app1);
            }
            return contentWebApps;
        }

        internal bool SolutionExistsInSiteCollection(string solutionName, string siteCollectionName)
        {
            try
            {
                using (SPSite site = new SPSite(siteCollectionName))
                {
                    SPList catalog = site.RootWeb.GetCatalog(SPListTemplateType.SolutionCatalog);
                    foreach (SPFile file in catalog.RootFolder.Files)
                    {
                        if (file.Name.Equals(solutionName, StringComparison.InvariantCultureIgnoreCase))
                        {
                            //file exists in solution gallery
                            return true;
                        }
                    }
                }
            }
            catch { }
            return false;
        }

        internal SPWebApplication GetWebAppByName(string webAppName)
        {
            foreach (SPWebApplication webapp in SPWebService.AdministrationService.WebApplications)
            {
                try
                {
                    foreach (SPAlternateUrl alterurl in webapp.AlternateUrls)
                    {
                        if (alterurl.IncomingUrl == webAppName)
                        {
                            return webapp;
                        }
                    }

                }
                catch (Exception)
                {
                }
            }
            foreach (SPWebApplication webapp in SPWebService.ContentService.WebApplications)
            {
                try
                {
                    foreach (SPAlternateUrl alterurl in webapp.AlternateUrls)
                    {
                        if (alterurl.IncomingUrl == webAppName)
                        {
                            return webapp;
                        }
                    }

                }
                catch (Exception)
                {
                }
            }
            return null;
        }

        internal bool WebApplicationExistsOnServer(string webAppName)
        {
            try
            {
                if (GetWebAppByName(webAppName) != null)
                {
                    return true;
                }
            }
            catch { }
            return false;
        }

        internal bool SiteCollectionExistsOnServer(string siteCollectionName)
        {
            try
            {
                using (SPSite site = new SPSite(siteCollectionName))
                {
                    if (site != null)
                    {
                        return true;
                    }
                }
            }
            catch { }
            return false;
        }

        internal bool SolutionExistsOnServer(string solutionName)
        {
            if (SPFarm.Local.Solutions[solutionName] != null)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Returns the solution names without the path (only solution.wsp)
        /// </summary>
        /// <returns></returns>
        internal string[] GetSolutionNames(string Solutions)
        {
            List<string> result = new List<string>();

            string[] res = GetArrayFromString(Solutions);

            for (int i = 0; i < res.Length; i++)
            {
                res[i] = GetSolutionNameFromPath(res[i]);
                if (res[i].Trim() != "")
                {
                    result.Add(res[i]);
                }
            }

            return result.ToArray();
        }


        /// <summary>
        /// Returns the elements in a string as array (and removes empty elements)
        /// </summary>
        /// <returns></returns>
        internal string[] GetArrayFromString(string Solutions)
        {
            List<string> result = new List<string>();

            if (Solutions != null)
            {
                char[] sep = new char[] { ';' };
                string[] res = Solutions.Split(sep);
                for (int i = 0; i < res.Length; i++)
                {
                    if (res[i].Trim() != "")
                    {
                        result.Add(res[i]);
                    }
                }
            }

            return result.ToArray();
        }

        internal string GetSolutionNameFromPath(string solutionPath)
        {
            if (solutionPath.Contains("/"))
            {
                return solutionPath.Substring(solutionPath.LastIndexOf("/") + 1);
            }
            if (solutionPath.Contains("\\"))
            {
                return solutionPath.Substring(solutionPath.LastIndexOf("\\") + 1);
            }
            return solutionPath;
        }

        /// <summary>
        /// checks if any exceptions occured, if yes the task will be stopped
        /// </summary>
        internal void HandleExceptions()
        {
            if (exceptions.Count > 0)
            {
                string message = "";
                foreach (string exception in exceptions)
                {
                    message += exception + Environment.NewLine;
                }
                LogException(message);
                throw new Exception("Operation failed");
            }
        }

        internal Collection<SPWebApplication> GetWebApplicationsFromNames(string[] webApplicationNames)
        {
            Collection<SPWebApplication> webApps = new Collection<SPWebApplication>();

            foreach (string webAppName in webApplicationNames)
            {
                SPWebApplication webApp = GetWebAppByName(webAppName);
                if (webApp == null)
                {
                    exceptions.Add("WebApplication '" + webAppName + "' not found");
                }
                else
                {
                    webApps.Add(webApp);
                }
            }
            return webApps;
        }


        /// <summary>
        /// Checks if all given solutions exist in solution store
        /// </summary>
        /// <returns></returns>
        internal Collection<SPSolution> GetSPSolutions(string[] solutionNames)
        {
            Collection<SPSolution> solutions = new Collection<SPSolution>();
            foreach (string solutionName in solutionNames)
            {
                SPSolution solution = SPFarm.Local.Solutions[solutionName];
                if (solution == null)
                {
                    exceptions.Add("Solution '" + solutionName + "' not found in solution store");
                }
                else
                {
                    solutions.Add(solution);
                }
            }
            return solutions;
        }

        internal SPSolution GetSPSolutionFromPath(string solutionPath)
        {
            string solutionName = GetSolutionNameFromPath(solutionPath);
            return SPFarm.Local.Solutions[solutionName];
        }

        internal bool DeploymentIsInProgress(string solutionName)
        {
            SPSolution solution = GetSPSolution(solutionName);
            if (solution != null)
            {
                if (solution.LastOperationResult == SPSolutionOperationResult.NoOperationPerformed)
                {
                    if (!solution.JobExists)
                    {
                        return false;
                    }
                }
                if (solution.LastOperationResult == SPSolutionOperationResult.NoOperationPerformed)
                {
                    return true;
                }
            }
            return false;
        }

        internal void WaitForFinishedDeployment(string solutionName)
        {
            SPSolution solution = GetSPSolution(solutionName);
            if (solution != null)
            {
                LogMessage("Waiting for solution '" + solution.Name + "'", LogLevel.Normal);

                int maxwait = 200;
                int sleepSeconds = 3;
                int currentwait = 0;

                while (currentwait < maxwait)
                {
                    currentwait++;
                    if (!FarmIsRunning())
                    {
                        //wait until farm is up again
                        LogMessage("Waiting for farm", LogLevel.Low);
                    }
                    else if (JobExistsForSolution(solutionName))
                    {
                        break;
                    }
                    LogMessage("Waiting for finished deployment of solution '" + solution.Name + "'", LogLevel.Low);
                    System.Threading.Thread.Sleep(sleepSeconds * 1000);
                }

                //wait that the status is deployed or failed
                currentwait = 0;
                while (currentwait < maxwait)
                {
                    currentwait++;
                    if (!FarmIsRunning())
                    {
                        //wait until farm is up again
                        LogMessage("Waiting for farm", LogLevel.Low);
                    }
                    else if (!DeploymentIsInProgress(solutionName))
                    {
                        break;
                    }
                    LogMessage("Waiting for finished deployment of solution '" + solution.Name + "'", LogLevel.Low);
                    System.Threading.Thread.Sleep(sleepSeconds * 1000);
                }
            }

            return;
        }

        internal bool FarmIsRunning()
        {
            LogMessage("Check farm is running.", LogLevel.Low);
            if (SPFarm.Local != null)
            {
                return true;
            }
            return false;
        }

        internal SPSolution GetSPSolution(string solutionName)
        {
            if (SPFarm.Local != null)
            {
                SPSolution solution = SPFarm.Local.Solutions[solutionName];
                if (solution != null)
                {
                    return solution;
                }
            }
            return null;
        }

        internal void WaitForFinishedUndeployment(string solutionName)
        {

            LogMessage("Waiting for solution '" + solutionName + "'", LogLevel.Normal);

            int maxwait = 200;
            int sleepSeconds = 3;
            int currentwait = 0;

            while (currentwait < maxwait)
            {
                //reload the SPSolution because server restarts and pointer is lost!!!

                currentwait++;
                if (!FarmIsRunning())
                {
                    //wait until farm is up again
                    LogMessage("Waiting for farm", LogLevel.Low);
                }
                else
                {
                    if (JobExistsForSolution(solutionName))
                    {
                        LogMessage("Waiting for finished undeployment of solution '" + solutionName + "'", LogLevel.Low);
                    }
                    else
                    {
                        //no job exists, finished
                        return;
                    }
                }
                System.Threading.Thread.Sleep(sleepSeconds * 1000);
            }
        }

        private bool JobExistsForSolution(string solutionName)
        {
            SPSolution solution = GetSPSolution(solutionName);
            if (solution != null)
            {
                if (solution.JobExists)
                {
                    return true;
                }
            }
            else
            {
                LogMessage("JobExistsForSolution: Solution '" + solutionName + "' not found.", LogLevel.Low);
            }
            return false;
        }

        internal void ExecAdmSvcJobs(bool local, bool quiet)
        {
            int timeout = 10;
            LogMessage("ExecAdmSvcJobs", LogLevel.Low);
            foreach (SPServer server in SPFarm.Local.Servers)
            {
                // Only look at servers with a valid role.
                if (server.Role == SPServerRole.Invalid)
                    continue;

                // Don't need to check locally as we just ran the OOTB command locally so skip the local server.
                if (server.Id.Equals(SPServer.Local.Id))
                    continue;

                bool stillExecuting;

                int runningCount = 0;
                do
                {
                    if (!quiet)
                    {
                        LogMessage("Checking jobs on " + server.Name, LogLevel.Low);
                    }
                    runningCount++;
                    stillExecuting = CheckApplicableRunningJobs(server, quiet);
                    // If jobs are still executing then sleep for 1 second.
                    if (stillExecuting)
                    {
                        Thread.Sleep(1000);
                    }
                    if (runningCount > timeout)
                    {
                        stillExecuting = false;
                        return;
                    }
                } while (stillExecuting);
            }
        }

        /// <summary>
        /// Checks for applicable running jobs.
        /// </summary>
        /// <param name="server">The server.</param>
        /// <param name="quiet">if set to <c>true</c> [quiet].</param>
        /// <returns></returns>
        private bool CheckApplicableRunningJobs(SPServer server, bool quiet)
        {
            try
            {
                foreach (KeyValuePair<Guid, SPService> current in GetProvisionedServices(server))
                {
                    SPService service = current.Value;
                    SPAdministrationServiceJobDefinitionCollection definitions = new SPAdministrationServiceJobDefinitionCollection(service);
                    if (CheckApplicableRunningJobs(server, definitions, quiet))
                        return true; // We've found running jobs so no point looking any further.

                    SPWebService service2 = service as SPWebService;
                    if (service2 != null)
                    {
                        foreach (SPWebApplication webApplication in service2.WebApplications)
                        {
                            definitions = new SPAdministrationServiceJobDefinitionCollection(webApplication);
                            if (CheckApplicableRunningJobs(server, definitions, quiet))
                                return true;
                        }
                    }
                }
                return false;
            }
            catch
            {
                //in case of exception return true to avoid dead lock
                return true;
            }
        }

        /// <summary>
        /// Checks for applicable running jobs.
        /// </summary>
        /// <param name="server">The server.</param>
        /// <param name="jds">The job definitions to consider.</param>
        /// <param name="quiet">if set to <c>true</c> [quiet].</param>
        /// <returns></returns>
        private bool CheckApplicableRunningJobs(SPServer server, SPAdministrationServiceJobDefinitionCollection jds, bool quiet)
        {
            bool stillExecuting = false;
            foreach (SPJobDefinition definition in jds)
            {
                if (string.IsNullOrEmpty(definition.Name))
                    continue;
                bool isApplicable = false;
                if (!definition.IsDisabled)
                    isApplicable = ((definition.Server == null) || definition.Server.Id.Equals(server.Id));
                if (!isApplicable)
                {
                    // If it's not applicable then we don't really care if it's running or not.
                    continue;
                }

                if (!quiet)
                    Console.Write("Waiting on {0}.\r\n", definition.Name);
                stillExecuting = true;
            }
            return stillExecuting;
        }
        /// <summary>
        /// Gets the provisioned services.
        /// </summary>
        /// <param name="server">The server.</param>
        /// <returns></returns>
        private Dictionary<Guid, SPService> GetProvisionedServices(SPServer server)
        {
            Dictionary<Guid, SPService> dictionary = new Dictionary<Guid, SPService>(8);
            foreach (SPServiceInstance serviceInstance in server.ServiceInstances)
            {
                SPService service = serviceInstance.Service;
                if (serviceInstance.Status == SPObjectStatus.Online)
                {
                    if (dictionary.ContainsKey(service.Id))
                        continue;
                    dictionary.Add(service.Id, service);
                }
            }
            return dictionary;
        }

        /// <summary>
        /// This class mimics the internal equivalent and is used because the base class is abstract.
        /// </summary>
        internal class SPAdministrationServiceJobDefinitionCollection : SPPersistedChildCollection<SPAdministrationServiceJobDefinition>
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="SPAdministrationServiceJobDefinitionCollection"/> class.
            /// </summary>
            /// <param name="service">The service.</param>
            internal SPAdministrationServiceJobDefinitionCollection(SPService service)
                : base(service)
            {
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="SPAdministrationServiceJobDefinitionCollection"/> class.
            /// </summary>
            /// <param name="webApplication">The web application.</param>
            internal SPAdministrationServiceJobDefinitionCollection(SPWebApplication webApplication)
                : base(webApplication)
            {
            }
        }

        internal bool EnsureRunning(string serviceName)
        {
            ServiceController sc = new ServiceController(serviceName);
            if (sc.Status == ServiceControllerStatus.Running)
            {
                return true;
            }
            else
            {
                LogMessage("Starting service...", LogLevel.Normal);

                sc.Start();
                sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 0, 10));

                if (sc.Status == ServiceControllerStatus.Running)
                {
                    return true;
                }
            }
            return false;
        }
    }
}
